﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TreeBuilder.Contracts;
using System.Diagnostics;
using TreeBuilder;
using TreeBuilderTest.Entities;
using MvvmTreeView;
using AbstraX.ClientInterfaces;
using SolutionFramework.Entities.TreeBuilder.Client;
using System.ServiceModel.DomainServices.Client;
using System.Collections.ObjectModel;
using System.IO;
using ServerEntities = SolutionFramework.Entities;
using TreeBuilder.Services;
using System.Threading;
using System.Windows.Threading;
using AbstraXLocalTest;
using System.Linq.Expressions;
using UIObjects.TreeViewInterfaces;
using System.Windows.Browser;
using System.Reflection;
using MvvmTreeView.Converters;
using System.Windows.Media.Imaging;
using SolutionFramework.ImageServiceReference;

namespace TreeBuilderTest.Services
{
    public class TreeBuilderService : ITreeBuilderService
    {
        private RootsCollection roots;
        private ServerEntities.SolutionFrameworkDomainContext context;
        private BaseSurrogate baseSurrogate;
        private string imageEditorURL;

        public TreeBuilderService()
        {
            context = new ServerEntities.SolutionFrameworkDomainContext();
            baseSurrogate = new BaseSurrogate();

            var invokeOperation = context.GetImageEditorUrl();

            invokeOperation.Completed += (sender, e) =>
            {
                imageEditorURL = invokeOperation.Value;
            };
        }

        public IRootsCollection TreeBuilderRoots
        {
            get 
            {
                if (roots == null)
                {
                    var rootsQuery = context.GetTreeBuilderRootsQuery();

                    roots = new RootsCollection();

                    var rootsLoadOp = context.Load(rootsQuery, (rootsResult) =>
                    {
                        foreach (var root in rootsResult.Entities)
                        {
                            roots.Add(new TreeBuilderRoot(this, root));
                        }
                    }
                    , null);
                }

                return roots;
            }
        }

        public NodeInvokeOperation<bool> HasImage(ITreeNodePropertyBinding propertyBinding)
        {
            var nodeInvokeOperation = new NodeInvokeOperation<bool>();
            var serverEntity = (ServerEntities.TreeNodePropertyBinding) propertyBinding.ServerEntity;
            var invokeOperation = context.HasImagePropertyBinding(serverEntity);
            
            invokeOperation.Completed += (sender, e) =>
            {
                nodeInvokeOperation.SetInvoked(propertyBinding, invokeOperation.Value);
            };

            return nodeInvokeOperation;
        }

        public NodeInvokeOperation<bool> HasImage(ITreeNodeMenuItem menuItem)
        {
            var nodeInvokeOperation = new NodeInvokeOperation<bool>();
            var serverEntity = (ServerEntities.TreeNodeMenuItem)menuItem.ServerEntity;
            var invokeOperation = context.HasImageMenuItem(serverEntity);

            invokeOperation.Completed += (sender, e) =>
            {
                nodeInvokeOperation.SetInvoked(menuItem, invokeOperation.Value);
            };

            return nodeInvokeOperation;
        }

        public NodeInvokeOperation<ITreeNodeImage> GetImage(ITreeNodePropertyBinding propertyBinding)
        {
            var nodeInvokeOperation = new NodeInvokeOperation<ITreeNodeImage>();
            var serverEntity = (ServerEntities.TreeNodePropertyBinding)propertyBinding.ServerEntity;
            var imagesQuery = context.GetTreeNodeImagesQuery().Where(i => i.TreeNodeBaseID == serverEntity.TreeNodeBaseID);

            var imagesLoadOp = context.Load(imagesQuery, (imagesResult) =>
            {
                if (imagesResult.Entities.Count() == 1)
                {
                    nodeInvokeOperation.SetInvoked(propertyBinding, new TreeNodeImage(this, imagesResult.Entities.Single()));
                }
                else
                {
                    nodeInvokeOperation.SetInvoked(propertyBinding, null);
                }
            }, false);

            return nodeInvokeOperation;
        }

        public T ConvertImage<T>(byte[] imageData)
        {
            var converter = new ByteArrayToBitMapConverter();
            var image = (T) converter.Convert(imageData, typeof(T), null, null);

            return image;
        }

        public NodeInvokeOperation<ITreeNodeImage> GetImage(ITreeNodeMenuItem menuItem)
        {
            var nodeInvokeOperation = new NodeInvokeOperation<ITreeNodeImage>();
            var serverEntity = (ServerEntities.TreeNodeMenuItem)menuItem.ServerEntity;
            var imagesQuery = context.GetTreeNodeImagesQuery().Where(i => i.TreeNodeBaseID == serverEntity.TreeNodeBaseID);

            var imagesLoadOp = context.Load(imagesQuery, (imagesResult) =>
            {
                if (imagesResult.Entities.Count() == 1)
                {
                    nodeInvokeOperation.SetInvoked(menuItem, new TreeNodeImage(this, imagesResult.Entities.Single()));
                }
                else
                {
                    nodeInvokeOperation.SetInvoked(menuItem, null);
                }
            }, false);

            return nodeInvokeOperation;
        }

        public SubmitOperation DeleteImage(ITreeNodeImage image)
        {
            var entity = ((ITreeNodeServerEntity)image).ServerEntity;

            context.TreeNodeImages.Remove(entity);

            return context.SubmitChanges();
        }

        public SubmitOperation DetachImage(ITreeNodeImage image)
        {
            var entity = (ServerEntities.TreeNodeImage) ((ITreeNodeServerEntity)image).ServerEntity;

            entity.TreeNodeBase = null;
            entity.TreeNodeBaseID = null;

            return context.SubmitChanges();
        }

        public void ReportException(ITreeNodeBase node, Exception ex)
        {
            if (node is ITreeNodePropertyBinding)
            {
                var treeNodePropertyBinding = (ITreeNodePropertyBinding) node;

                node.Text = treeNodePropertyBinding.PropertyType.ToString() + " -> [" + ex.ToString() + "]";
            }
        }

        public NodeInvokeOperation<Guid> GetServerImageFormat(ITreeNodeImage image)
        {
            var nodeInvokeOperation = new NodeInvokeOperation<Guid>();
            var entity = (ITreeNodeServerEntity)image;

            var imageServiceClient = new ImageServiceClient();

            imageServiceClient.FormatFromFileNameCompleted += (sender, e) =>
            {
                nodeInvokeOperation.SetInvoked(entity, (Guid) e.Result);
            };

            imageServiceClient.FormatFromFileNameAsync(image.FileName);

            return nodeInvokeOperation;
        }

        public NodeInvokeOperation<TreeNodeImageFormat> GetTreeNodeImageFormat(ITreeNodeImage image)
        {
            var nodeInvokeOperation = new NodeInvokeOperation<TreeNodeImageFormat>();
            var imageServiceClient = new ImageServiceClient();
            var entity = (ITreeNodeServerEntity)image;

            imageServiceClient.ExtensionFromFormatCompleted += (sender, e) =>
            {
                var format = TreeNodeImageFormat.Unknown;

                switch (e.Result)
                {
                    case "jpg":
                        format = TreeNodeImageFormat.Jpeg;
                        break;
                    case "png":
                        format = TreeNodeImageFormat.Png;
                        break;
                }

                nodeInvokeOperation.SetInvoked(entity, format);
            };

            imageServiceClient.ExtensionFromFormatAsync((Guid) image.ImageServerFormat);

            return nodeInvokeOperation;
        }

        public NodeCreateOperation<ITreeNodePropertyBinding> CreateImageBindingNode(ITreeNodeBase parent, ITreeNodeImage treeNodeImage)
        {
            var entity = (ITreeNodeServerEntity)parent;
            var nodeCreateOperation = new NodeCreateOperation<ITreeNodePropertyBinding>();

            try
            {
                var treeNodeBase = new ServerEntities.TreeNodeBase
                {
                    ParentNodeID = Guid.Parse(entity.ID),
                    Name = TreeNodePropertyType.Image.ToString() + " -> [" + treeNodeImage.FileName + "]"
                };

                var treeNodeNew = new ServerEntities.TreeNodePropertyBinding
                {
                    PropertyType = (int)TreeNodePropertyType.Image,
                    StockBinding = (int)TreeNodePropertyStockBinding.FromImageUpload,
                    TreeNodeBase = treeNodeBase
                };

                context.TreeNodePropertyBindings.Add(treeNodeNew);

                var submitOperationNode = context.SubmitChanges();

                submitOperationNode.Completed += (sender2, e2) =>
                {
                    var treeNodeImageServerEntity = (ServerEntities.TreeNodeImage) treeNodeImage.ServerEntity;

                    context.TreeNodeImages.Attach(treeNodeImageServerEntity);

                    treeNodeImageServerEntity.TreeNodeBaseID = treeNodeBase.TreeNodeBaseID;

                    var submitOperationImage = context.SubmitChanges();

                    submitOperationImage.Completed += (sender3, e3) =>
                    {
                        nodeCreateOperation.SetCreated(parent, new TreeNodePropertyBinding(this, treeNodeNew));
                    };
                };

            }
            catch (Exception ex)
            {
                throw new Exception("Could not create property binding node", ex);
            }

            return nodeCreateOperation;
        }

        public NodeImageLoadOperation LoadImageEditor(ITreeNodeBase parent, string fileName)
        {
            var imageLoadOperation = new NodeImageLoadOperation();

            if (!HtmlPage.IsPopupWindowAllowed)
            {
                imageLoadOperation.Exception = new Exception("Popup windows must be allowed.");
                return imageLoadOperation;
            }

            if (imageEditorURL != null)
            {
                var key = Guid.NewGuid();
                var entity = (ITreeNodeServerEntity)parent;
                var url = imageEditorURL + "?Key=" + key.ToString() + "&FileName=" + fileName;
                var window = HtmlPage.PopupWindow(new Uri(url), "_blank", null);
                var contextImages = new ServerEntities.SolutionFrameworkDomainContext();
                var imageServiceClient = new ImageServiceClient();

                if (window == null)
                {
                    imageLoadOperation.Exception = new Exception("Error loading image editor");
                    return imageLoadOperation;
                }

                var timer = new DispatcherTimer();
                EventHandler<IsUploadCancelledCompletedEventArgs> handler = null;

                handler = (sender, e) =>
                {
                    if (e.Result)
                    {
                        timer.Stop();
                        imageServiceClient.IsUploadCancelledCompleted -= handler;

                        // in case of lingering requests that have reopened the queue

                        imageServiceClient.CancelUploadAsync(key);
                    }
                };

                imageServiceClient.IsUploadCancelledCompleted += handler;

                timer.Tick += (s, args) =>
                {
                    timer.Stop();

                    if (Monitor.TryEnter(contextImages))
                    {
                        try
                        {
                            var imagesQuery = contextImages.GetTreeNodeImagesQuery().Where(t => t.ImageKey == key);

                            var imagesLoadOp = contextImages.Load(imagesQuery, (imagesResult) =>
                            {
                                if (imagesResult.Entities.Count() == 1)
                                {
                                    var image = imagesResult.Entities.Single();
                                    var treeNodeImage = new TreeNodeImage(this, image);

                                    contextImages.TreeNodeImages.Detach(image);

                                    imageLoadOperation.SetLoaded(parent, treeNodeImage);

                                    return;
                                }
                                else if (imagesResult.Entities.Count() > 1)
                                {
                                    Debugger.Break();
                                }
                                else
                                {
                                    imageServiceClient.IsUploadCancelledAsync(key);
                                }

                                Monitor.Exit(contextImages);
                                timer.Start();

                            }
                            , null);
                        }
                        catch (Exception ex)
                        {
                            Debugger.Break();
                            Monitor.Exit(contextImages);
                        }
                    }
                };

                timer.Interval = new TimeSpan(0, 0, 0, 0, 1000); // one second
                timer.Start();
            }

            return imageLoadOperation;
        }

        public ObservableCollection<ITreeNodeBase> LoadChildren(ITreeNodeServerEntity entity)
        {
            /*
             *  todo - this method needs 3 things:
             *  
             *  1. accomodation for what parent-child scenarios make sense.  This is currently a hail mary approach.
             *  2. this method is not necessarily mult-user safe.  Queries can return results that may be the result of changes by another user.
             *  3. a timout mechanism.  If a query hangs, the system timer will loop forever
             * 
             */

            var childEntities = new ObservableCollection<ITreeNodeBase>();
            var loadMap = 0;
            const int NUMBER_OF_CHILD_QUERIES = 11;
            int shortcutCount = -1;

            var rootsQuery = context.GetTreeBuilderRootsQuery().Where(e => e.TreeNodeBase.ParentNodeID == Guid.Parse(entity.ID));
            var rootSourcesQuery = context.GetTreeNodeRootSourcesQuery().Where(e => e.TreeNodeBase.ParentNodeID == Guid.Parse(entity.ID));
            var advancedQueriesQuery = context.GetTreeNodeAdvancedQueriesQuery().Where(e => e.TreeNodeBase.ParentNodeID == Guid.Parse(entity.ID));
            var attributeNodesQuery = context.GetTreeNodeAttributeNodesQuery().Where(e => e.TreeNodeBase.ParentNodeID == Guid.Parse(entity.ID));
            var elementNodesQuery = context.GetTreeNodeElementNodesQuery().Where(e => e.TreeNodeBase.ParentNodeID == Guid.Parse(entity.ID));
            var foldersQuery = context.GetTreeNodeFoldersQuery().Where(e => e.TreeNodeBase.ParentNodeID == Guid.Parse(entity.ID));
            var propertyBindingsQuery = context.GetTreeNodePropertyBindingsQuery().Where(e => e.TreeNodeBase.ParentNodeID == Guid.Parse(entity.ID));
            var transferMappingsQuery = context.GetTreeNodeTransferMappingsQuery().Where(e => e.TreeNodeBase.ParentNodeID == Guid.Parse(entity.ID));
            var queriesQuery = context.GetTreeNodeQueriesQuery().Where(e => e.TreeNodeBase.ParentNodeID == Guid.Parse(entity.ID));
            var menuItemsQuery = context.GetTreeNodeMenuItemsQuery().Where(e => e.TreeNodeBase.ParentNodeID == Guid.Parse(entity.ID));
            var shortcutsQuery = context.GetTreeNodeBasesQuery().Where(e => e.ParentNodeID == Guid.Parse(entity.ID) && e.ShortcutNodeID != null); 

            var rootsLoadOp = context.Load(rootsQuery, (result) =>
            {
                Monitor.Enter(this);

                foreach (var root in result.Entities)
                {
                    childEntities.Add(new TreeBuilderRoot(this, root));
                }

                loadMap |= (int)Math.Pow(2, 0);
                Monitor.Exit(this);
            }
            , null);

            var rootSourcesLoadOp = context.Load(rootSourcesQuery, (result) =>
            {
                Monitor.Enter(this);

                foreach (var rootSources in result.Entities)
                {
                    childEntities.Add(new TreeNodeRootSource(this, rootSources));
                }

                loadMap |= (int)Math.Pow(2, 1);
                Monitor.Exit(this);
            }
            , null);

            var advancedQueriesLoadOp = context.Load(advancedQueriesQuery, (result) =>
            {
                Monitor.Enter(this);

                foreach (var advancedQuery in result.Entities)
                {
                    childEntities.Add(new TreeNodeAdvancedQuery(this, advancedQuery));
                }

                loadMap |= (int)Math.Pow(2, 2);
                Monitor.Exit(this);
            }
            , null);

            var attributeNodesLoadOp = context.Load(attributeNodesQuery, (result) =>
            {
                Monitor.Enter(this);

                foreach (var attributeNode in result.Entities)
                {
                    childEntities.Add(new TreeNodeAttributeNode(this, attributeNode));
                }

                loadMap |= (int)Math.Pow(2, 3);
                Monitor.Exit(this);
            }
            , null);

            var elementNodesLoadOp = context.Load(elementNodesQuery, (result) =>
            {
                Monitor.Enter(this);

                foreach (var elementNode in result.Entities)
                {
                    childEntities.Add(new TreeNodeElementNode(this, elementNode));
                }

                loadMap |= (int)Math.Pow(2, 4);
                Monitor.Exit(this);
            }
            , null);

            var foldersLoadOp = context.Load(foldersQuery, (result) =>
            {
                Monitor.Enter(this);

                foreach (var folder in result.Entities)
                {
                    childEntities.Add(new TreeNodeFolder(this, folder));
                }

                loadMap |= (int)Math.Pow(2, 5);
                Monitor.Exit(this);
            }
            , null);

            var propertyBindingsLoadOp = context.Load(propertyBindingsQuery, (result) =>
            {
                Monitor.Enter(this);

                foreach (var propertyBinding in result.Entities)
                {
                    childEntities.Add(new TreeNodePropertyBinding(this, propertyBinding));
                }

                loadMap |= (int)Math.Pow(2, 6);
                Monitor.Exit(this);
            }
            , null);

            var transferMappingsLoadOp = context.Load(transferMappingsQuery, (result) =>
            {
                Monitor.Enter(this);

                foreach (var transferMapping in result.Entities)
                {
                    childEntities.Add(new TreeNodeTransferMapping(this, transferMapping));
                }

                loadMap |= (int)Math.Pow(2, 7);
                Monitor.Exit(this);
            }
            , null);

            var queriesLoadOp = context.Load(queriesQuery, (result) =>
            {
                Monitor.Enter(this);

                foreach (var query in result.Entities)
                {
                    childEntities.Add(new TreeNodeQuery(this, query));
                }

                loadMap |= (int)Math.Pow(2, 8);
                Monitor.Exit(this);
            }
            , null);

            var menuItemsLoadOp = context.Load(menuItemsQuery, (result) =>
            {
                Monitor.Enter(this);

                foreach (var query in result.Entities)
                {
                    childEntities.Add(new TreeNodeMenuItem(this, query));
                }

                loadMap |= (int)Math.Pow(2, 9);
                Monitor.Exit(this);
            }
            , null);

            var shortcutsLoadOp = context.Load(shortcutsQuery, (result) =>
            {
                Monitor.Enter(this);
                shortcutCount = result.Entities.Count();

                foreach (var shortcutEntity in result.Entities)
                {
                    var attributeShortcutsQuery = context.GetTreeNodeAttributeNodesQuery().Where(e => e.TreeNodeBaseID == shortcutEntity.ShortcutNodeID);
                    var elementShorcutsQuery = context.GetTreeNodeElementNodesQuery().Where(e => e.TreeNodeBaseID == shortcutEntity.ShortcutNodeID);

                    var attributeShortcutsLoadOp = context.Load(attributeShortcutsQuery, (resultShortcut) =>
                    {
                        if (resultShortcut.Entities.Any())
                        {
                            var attributeShortcut = resultShortcut.Entities.Single();
                            var entityAttribute = new ServerEntities.TreeNodeAttributeNode
                            {
                                TreeNodeBase = shortcutEntity,
                                Attribute = attributeShortcut.Attribute
                            };

                            var newNode = new TreeNodeAttributeNode(this, entityAttribute)
                            {
                                ShortcutNode = new TreeNodeAttributeNode(this, attributeShortcut)
                            };

                            Monitor.Enter(this);

                            childEntities.Add(newNode);
                            shortcutCount--;

                            Monitor.Exit(this);
                        }
                    }
                    , null);

                    var elementShortcutsLoadOp = context.Load(elementShorcutsQuery, (resultShortcut) =>
                    {
                        if (resultShortcut.Entities.Any())
                        {
                            var elementShortcut = resultShortcut.Entities.Single();
                            var entityElement = new ServerEntities.TreeNodeElementNode
                            {
                                TreeNodeBase = shortcutEntity,
                                Element = elementShortcut.Element
                            };

                            var newNode = new TreeNodeElementNode(this, entityElement)
                            {
                                ShortcutNode = new TreeNodeElementNode(this, elementShortcut)
                            };

                            Monitor.Enter(this);

                            childEntities.Add(newNode);
                            shortcutCount--;

                            Monitor.Exit(this);
                        }
                    }
                    , null);
                }

                loadMap |= (int)Math.Pow(2, 10);
                Monitor.Exit(this);
            }
            , null);

            DispatcherTimer timer = new DispatcherTimer();

            timer.Tick += (object s, EventArgs args) =>
            {
                if (Monitor.TryEnter(this))
                {
                    if (loadMap == Math.Pow(2, NUMBER_OF_CHILD_QUERIES) - 1)
                    {
                        if (shortcutCount == 0)
                        {
                            timer.Stop();

                            if (childEntities.Count == 0)
                            {
                                entity.NoChildren();
                            }
                        }
                    }
                }
            };

            timer.Interval = new TimeSpan(0, 0, 0, 0, 500); // half second
            timer.Start();

            return childEntities;
        }

        public byte[] LoadShortcutImage()
        {
            var stream = this.GetType().Assembly.GetManifestResourceStream("SolutionFramework.Images.Shortcut.png");

            var bytes = new byte[stream.Length];

            stream.Read(bytes, 0, (int)stream.Length);

            return bytes;
        }

        public byte[] LoadImageForItemType(string itemTypeName)
        {
            var imagePath = string.Empty;

            switch (itemTypeName)
            {
                case "SolutionFramework.Entities.TreeBuilder.Client.TreeBuilderRoot":
                    imagePath = "SolutionFramework.Images.AssembliesRoot.png";
                    break;
                case "SolutionFramework.Entities.TreeBuilder.Client.TreeNodeRootSource":
                    imagePath = "SolutionFramework.Images.AssembliesRoot.png";
                    break;
                case "SolutionFramework.Entities.TreeBuilder.Client.TreeNodeAttributeNode":
                    imagePath = "SolutionFramework.Images.AssembliesRoot.png";
                    break;
                case "SolutionFramework.Entities.TreeBuilder.Client.TreeNodeElementNode":
                    imagePath = "SolutionFramework.Images.AssembliesRoot.png";
                    break;
                case "SolutionFramework.Entities.TreeBuilder.Client.TreeNodePropertyBinding":
                    imagePath = "SolutionFramework.Images.AssembliesRoot.png";
                    break;
                case "SolutionFramework.Entities.TreeBuilder.Client.TreeNodeTransferMapping":
                    imagePath = "SolutionFramework.Images.AssembliesRoot.png";
                    break;
                case "SolutionFramework.Entities.TreeBuilder.Client.TreeNodeMenuItem":
                    imagePath = "SolutionFramework.Images.AssembliesRoot.png";
                    break;
            }

            var stream = this.GetType().Assembly.GetManifestResourceStream(imagePath);

            var bytes = new byte[stream.Length];

            stream.Read(bytes, 0, (int)stream.Length);

            return bytes;
        }

        public NodeInvokeOperation<TreeNodePropertyType> GetItemBindingPropertyTypes(ITreeNodeServerEntity parent)
        {
            var nodeInvokeOperation = new NodeInvokeOperation<TreeNodePropertyType>();

            var invokeOperation = context.GetItemBindingPropertyTypes(Guid.Parse(parent.ID));

            invokeOperation.Completed += (sender, e) =>
            {
                nodeInvokeOperation.SetInvoked(parent, (TreeNodePropertyType) invokeOperation.Value);
            };

            return nodeInvokeOperation;
        }

        public NodeInvokeOperation<TransferCreationMappingType> GetTransferCreationMappingTypes(ITreeNodeServerEntity parent)
        {
            var nodeInvokeOperation = new NodeInvokeOperation<TransferCreationMappingType>();

            var invokeOperation = context.GetItemTransferCreationMappingTypes(Guid.Parse(parent.ID));

            invokeOperation.Completed += (sender, e) =>
            {
                nodeInvokeOperation.SetInvoked(parent, (TransferCreationMappingType)invokeOperation.Value);
            };

            return nodeInvokeOperation;
        }

        public NodeCreateOperation<ITreeNodeAttributeNode> CreateAttributeNode(ITreeNodeBase parent, IAttribute attribute)
        {
            var entity = (ITreeNodeServerEntity)parent;
            var nodeCreateOperation = new NodeCreateOperation<ITreeNodeAttributeNode>();

            try
            {
                var treeNodeBase = new ServerEntities.TreeNodeBase
                {
                    ParentNodeID = Guid.Parse(entity.ID),
                    Name = attribute.Name
                };

                var treeNodeNew = new ServerEntities.TreeNodeAttributeNode
                {
                    Attribute = attribute.ID,
                    TreeNodeBase = treeNodeBase,
                };

                context.TreeNodeAttributeNodes.Add(treeNodeNew);

                var submitOperationNode = context.SubmitChanges();

                submitOperationNode.Completed += (sender2, e2) =>
                {
                    nodeCreateOperation.SetCreated(parent, new TreeNodeAttributeNode(this, treeNodeNew));
                };

            }
            catch (Exception ex)
            {
                throw new Exception("Could not create attribute node", ex);
            }

            return nodeCreateOperation;
        }

        public NodeCreateOperation<ITreeNodeElementNode> CreateElementNode(ITreeNodeBase parent, IElement element)
        {
            var entity = (ITreeNodeServerEntity)parent;
            var nodeCreateOperation = new NodeCreateOperation<ITreeNodeElementNode>();

            try
            {
                var treeNodeBase = new ServerEntities.TreeNodeBase
                {
                    ParentNodeID = Guid.Parse(entity.ID),
                    Name = element.Name
                };

                var treeNodeNew = new ServerEntities.TreeNodeElementNode
                {
                    Element = element.ID,
                    TreeNodeBase = treeNodeBase,
                };

                context.TreeNodeElementNodes.Add(treeNodeNew);

                var submitOperationNode = context.SubmitChanges();

                submitOperationNode.Completed += (sender2, e2) =>
                {
                    nodeCreateOperation.SetCreated(parent, new TreeNodeElementNode(this, treeNodeNew));
                };

            }
            catch (Exception ex)
            {
                throw new Exception("Could not create element node", ex);
            }

            return nodeCreateOperation;
        }

        public NodeCreateOperation<ITreeNodeFolder> CreateFolderNode(ITreeNodeBase parent, IBase folderSource)
        {
            var entity = (ITreeNodeServerEntity)parent;
            var nodeCreateOperation = new NodeCreateOperation<ITreeNodeFolder>();

            try
            {
                var treeNodeBase = new ServerEntities.TreeNodeBase
                {
                    ParentNodeID = Guid.Parse(entity.ID),
                    Name = folderSource.Name
                };

                var treeNodeNew = new ServerEntities.TreeNodeFolder
                {
                    FolderItemSource = folderSource.ID,
                    TreeNodeBase = treeNodeBase,
                };

                context.TreeNodeFolders.Add(treeNodeNew);

                var submitOperationNode = context.SubmitChanges();

                submitOperationNode.Completed += (sender2, e2) =>
                {
                    nodeCreateOperation.SetCreated(parent, new TreeNodeFolder(this, treeNodeNew));
                };

            }
            catch (Exception ex)
            {
                throw new Exception("Could not create folder node", ex);
            }

            return nodeCreateOperation;
        }

        public NodeCreateOperation<ITreeNodePropertyBinding> CreatePropertyBindingNode(ITreeNodeBase parent, TreeNodePropertyType propertyType, IElement element)
        {
            var entity = (ITreeNodeServerEntity)parent;
            var nodeCreateOperation = new NodeCreateOperation<ITreeNodePropertyBinding>();

            try
            {
                var treeNodeBase = new ServerEntities.TreeNodeBase
                {
                    ParentNodeID = Guid.Parse(entity.ID),
                    Name = propertyType.ToString() + " -> " + element.Name
                };

                var treeNodeNew = new ServerEntities.TreeNodePropertyBinding
                {
                    PropertySource = element.ID,
                    PropertyType = (int)propertyType,
                    TreeNodeBase = treeNodeBase
                };

                context.TreeNodePropertyBindings.Add(treeNodeNew);

                var submitOperationNode = context.SubmitChanges();

                submitOperationNode.Completed += (sender2, e2) =>
                {
                    nodeCreateOperation.SetCreated(parent, new TreeNodePropertyBinding(this, treeNodeNew));
                };

            }
            catch (Exception ex)
            {
                throw new Exception("Could not create property binding node", ex);
            }

            return nodeCreateOperation;
        }

        public NodeCreateOperation<ITreeNodePropertyBinding> CreatePropertyBindingNode(ITreeNodeBase parent, TreeNodePropertyType propertyType, IAttribute attribute)
        {
            return CreatePropertyBindingNode(parent, propertyType, attribute, false);
        }
        
        public NodeCreateOperation<ITreeNodePropertyBinding> CreatePropertyBindingNode(ITreeNodeBase parent, TreeNodePropertyType propertyType, IAttribute attribute, bool sourceIsURLBinding)
        {
            var entity = (ITreeNodeServerEntity)parent;
            var nodeCreateOperation = new NodeCreateOperation<ITreeNodePropertyBinding>();

            try
            {
                var treeNodeBase = new ServerEntities.TreeNodeBase
                {
                    ParentNodeID = Guid.Parse(entity.ID),
                    Name = propertyType.ToString() + " -> " + attribute.Name
                };

                var treeNodeNew = new ServerEntities.TreeNodePropertyBinding
                {
                    PropertySource = attribute.ID,
                    PropertyType = (int) propertyType,
                    SourceIsURLBinding = sourceIsURLBinding,
                    TreeNodeBase = treeNodeBase
                };

                context.TreeNodePropertyBindings.Add(treeNodeNew);

                var submitOperationNode = context.SubmitChanges();

                submitOperationNode.Completed += (sender2, e2) =>
                {
                    nodeCreateOperation.SetCreated(parent, new TreeNodePropertyBinding(this, treeNodeNew));
                };

            }
            catch (Exception ex)
            {
                throw new Exception("Could not create property binding node", ex);
            }

            return nodeCreateOperation;
        }

        public NodeCreateOperation<ITreeNodePropertyBinding> CreatePropertyBindingNode(ITreeNodeBase parent, TreeNodePropertyType propertyType, TreeNodePropertyStockBinding stockBinding)
        {
            var entity = (ITreeNodeServerEntity)parent;
            var nodeCreateOperation = new NodeCreateOperation<ITreeNodePropertyBinding>();

            try
            {
                var treeNodeBase = new ServerEntities.TreeNodeBase
                {
                    ParentNodeID = Guid.Parse(entity.ID),
                    Name = propertyType.ToString() + " -> [" + stockBinding.ToString() + "]"
                };

                var treeNodeNew = new ServerEntities.TreeNodePropertyBinding
                {
                    PropertyType = (int)propertyType,
                    StockBinding = (int)stockBinding,
                    TreeNodeBase = treeNodeBase
                };

                context.TreeNodePropertyBindings.Add(treeNodeNew);

                var submitOperationNode = context.SubmitChanges();

                submitOperationNode.Completed += (sender2, e2) =>
                {
                    nodeCreateOperation.SetCreated(parent, new TreeNodePropertyBinding(this, treeNodeNew));
                };

            }
            catch (Exception ex)
            {
                throw new Exception("Could not create property binding node", ex);
            }

            return nodeCreateOperation;
        }

        public NodeCreateOperation<ITreeNodeQuery> CreateQueryNode(ITreeNodeBase parent, IBase baseObject)
        {
            var entity = (ITreeNodeServerEntity)parent;
            var nodeCreateOperation = new NodeCreateOperation<ITreeNodeQuery>();

            try
            {
                var treeNodeBase = new ServerEntities.TreeNodeBase
                {
                    ParentNodeID = Guid.Parse(entity.ID),
                    Name = baseObject.Name
                };

                var treeNodeNew = new ServerEntities.TreeNodeQuery
                {
                    QueryInput = baseObject.ID,
                    TreeNodeBase = treeNodeBase,
                };

                context.TreeNodeQueries.Add(treeNodeNew);

                var submitOperationNode = context.SubmitChanges();

                submitOperationNode.Completed += (sender2, e2) =>
                {
                    nodeCreateOperation.SetCreated(parent, new TreeNodeQuery(this, treeNodeNew));
                };

            }
            catch (Exception ex)
            {
                throw new Exception("Could not create query node", ex);
            }

            return nodeCreateOperation;
        }

        public NodeCreateOperation<ITreeNodeAdvancedQuery> CreateAdvancedQueryNode(ITreeNodeBase parent, IBase baseObject)
        {
            var entity = (ITreeNodeServerEntity)parent;
            var nodeCreateOperation = new NodeCreateOperation<ITreeNodeAdvancedQuery>();

            try
            {
                var treeNodeBase = new ServerEntities.TreeNodeBase
                {
                    ParentNodeID = Guid.Parse(entity.ID),
                    Name = baseObject.Name
                };

                var treeNodeNew = new ServerEntities.TreeNodeAdvancedQuery
                {
                    QueryInput = baseObject.ID,
                    TreeNodeBase = treeNodeBase,
                };

                context.TreeNodeAdvancedQueries.Add(treeNodeNew);

                var submitOperationNode = context.SubmitChanges();

                submitOperationNode.Completed += (sender2, e2) =>
                {
                    nodeCreateOperation.SetCreated(parent, new TreeNodeAdvancedQuery(this, treeNodeNew));
                };

            }
            catch (Exception ex)
            {
                throw new Exception("Could not create advanced query node", ex);
            }

            return nodeCreateOperation;
        }

        public NodeCreateOperation<ITreeNodeRootSource> CreateRootSourceNode(ITreeNodeBase parent, IBase baseObject)
        {
            var entity = (ITreeNodeServerEntity) parent;
            var nodeCreateOperation = new NodeCreateOperation<ITreeNodeRootSource>();

            try
            {
                var treeNodeBase = new ServerEntities.TreeNodeBase
                {
                    ParentNodeID = Guid.Parse(entity.ID),
                    Name = baseObject.Name
                };

                var treeNodeNew = new ServerEntities.TreeNodeRootSource
                {
                    Source = baseObject.ID,
                    TreeNodeBase = treeNodeBase,
                };

                context.TreeNodeRootSources.Add(treeNodeNew);

                var submitOperationNode = context.SubmitChanges();

                submitOperationNode.Completed += (sender2, e2) =>
                {
                    nodeCreateOperation.SetCreated(parent, new TreeNodeRootSource(this, treeNodeNew));
                };

            }
            catch (Exception ex)
            {
                throw new Exception("Could not create root source node", ex);
            }

            return nodeCreateOperation;
        }

        public NodeCreateOperation<ITreeNodeTransferMapping> CreateTransferCreationMappingNode(ITreeNodeBase parent, TransferCreationMappingType mappingType, IBase targetSource)
        {
            var entity = (ITreeNodeServerEntity)parent;
            var nodeCreateOperation = new NodeCreateOperation<ITreeNodeTransferMapping>();
            var field = typeof(TransferCreationMappingType).GetFields().Where(f => ((f.Attributes & System.Reflection.FieldAttributes.Static) > 0) && ((TransferCreationMappingType)f.GetValue(0)) == mappingType).Single();
            var mappingAttribute = (TransferCreationMappingAttribute)field.GetCustomAttributes(typeof(TransferCreationMappingAttribute), true).Single();

            try
            {
                var treeNodeBase = new ServerEntities.TreeNodeBase
                {
                    ParentNodeID = Guid.Parse(entity.ID),
                    Name = "'" + mappingAttribute.DisplayText + "' Mappings" + " -> " + parent.Text
                };

                var treeNodeNew = new ServerEntities.TreeNodeTransferMapping
                {
                    TransferTargetSource = targetSource.ID,
                    TransferMappingType = (int) mappingType,
                    TreeNodeBase = treeNodeBase
                };

                context.TreeNodeTransferMappings.Add(treeNodeNew);

                var submitOperationNode = context.SubmitChanges();

                submitOperationNode.Completed += (sender2, e2) =>
                {
                    nodeCreateOperation.SetCreated(parent, new TreeNodeTransferMapping(this, treeNodeNew));
                };

            }
            catch (Exception ex)
            {
                throw new Exception("Could not create transfer mapping node", ex);
            }

            return nodeCreateOperation;
        }

        public NodeCreateOperation<ITreeNodeMenuItem> CreateMenuItem(ITreeNodeBase parent, IBase menuSource)
        {
            var entity = (ITreeNodeServerEntity)parent;
            var nodeCreateOperation = new NodeCreateOperation<ITreeNodeMenuItem>();

            try
            {
                var treeNodeBase = new ServerEntities.TreeNodeBase
                {
                    ParentNodeID = Guid.Parse(entity.ID),
                    Name = "Context Menu"
                };

                var treeNodeNew = new ServerEntities.TreeNodeMenuItem
                {
                    MenuSource = menuSource.ID,
                    TreeNodeBase = treeNodeBase
                };

                context.TreeNodeMenuItems.Add(treeNodeNew);

                var submitOperationNode = context.SubmitChanges();

                submitOperationNode.Completed += (sender2, e2) =>
                {
                    nodeCreateOperation.SetCreated(parent, new TreeNodeMenuItem(this, treeNodeNew));
                };

            }
            catch (Exception ex)
            {
                throw new Exception("Could not create root source node", ex);
            }

            return nodeCreateOperation;
        }

        public NodeCreateOperation<ITreeNodeMenuItem> CreateMenuItem(ITreeNodeBase parent, IBase menuSource, TreeNodeMenuItemStockAction stockAction)
        {
            var entity = (ITreeNodeServerEntity)parent;
            var nodeCreateOperation = new NodeCreateOperation<ITreeNodeMenuItem>();
            var field = typeof(TreeNodeMenuItemStockAction).GetFields().Where(f => ((f.Attributes & System.Reflection.FieldAttributes.Static) > 0) && ((TreeNodeMenuItemStockAction)f.GetValue(0)) == stockAction).Single();
            var menuAttribute = (StockActionMenuAttribute)field.GetCustomAttributes(typeof(StockActionMenuAttribute), true).Single();

            try
            {
                var treeNodeBase = new ServerEntities.TreeNodeBase
                {
                    ParentNodeID = Guid.Parse(entity.ID),
                    Name = menuAttribute.DisplayText
                };

                var treeNodeNew = new ServerEntities.TreeNodeMenuItem
                {
                    MenuSource = menuSource.ID,
                    TreeNodeBase = treeNodeBase,
                    StockAction = (int) stockAction
                };

                context.TreeNodeMenuItems.Add(treeNodeNew);

                var submitOperationNode = context.SubmitChanges();

                submitOperationNode.Completed += (sender2, e2) =>
                {
                    nodeCreateOperation.SetCreated(parent, new TreeNodeMenuItem(this, treeNodeNew));
                };

            }
            catch (Exception ex)
            {
                throw new Exception("Could not menu item", ex);
            }

            return nodeCreateOperation;
        }

        public NodeCreateOperation<ITreeNodeMenuItem> CreateMenuItem(ITreeNodeBase parent, IBase menuSource, IOperation operation)
        {
            var entity = (ITreeNodeServerEntity)parent;
            var nodeCreateOperation = new NodeCreateOperation<ITreeNodeMenuItem>();

            try
            {
                var treeNodeBase = new ServerEntities.TreeNodeBase
                {
                    ParentNodeID = Guid.Parse(entity.ID),
                    Name = operation.Name
                };

                var treeNodeNew = new ServerEntities.TreeNodeMenuItem
                {
                    MenuSource = menuSource.ID,
                    TreeNodeBase = treeNodeBase
                };

                context.TreeNodeMenuItems.Add(treeNodeNew);

                var submitOperationNode = context.SubmitChanges();

                submitOperationNode.Completed += (sender2, e2) =>
                {
                    nodeCreateOperation.SetCreated(parent, new TreeNodeMenuItem(this, treeNodeNew));
                };

            }
            catch (Exception ex)
            {
                throw new Exception("Could not create root source node", ex);
            }

            return nodeCreateOperation;
        }

        public ObservableCollection<IElement> GetPotentialRootSources()
        {
            return new ObservableCollection<IElement>(baseSurrogate.RootElements);
        }

        public SubmitOperation UpdateImage(ITreeNodeImage image)
        {
            ServerEntities.TreeNodeImage serverImage = (ServerEntities.TreeNodeImage) image.ServerEntity;

            return context.SubmitChanges();
        }

        public SubmitOperation UpdateNode<T>(T node) where T : ITreeNodeBase
        {
            var entity = ((ITreeNodeServerEntity)node).ServerEntity;
            ServerEntities.TreeNodeBase treeNodeBase = null;

            switch (entity.GetType().Name)
            {
                case "TreeBuilderRoot":

                    var treeBuilderRoot = (ITreeBuilderRoot)node;
                    var treeBuilderRootEntity = ((ServerEntities.TreeBuilderRoot)entity);

                    treeNodeBase = treeBuilderRootEntity.TreeNodeBase;
                    break;

                case "TreeNodeAdvancedQuery":

                    var treeNodeAdvancedQuery = (ITreeNodeAdvancedQuery)node;
                    var treeNodeAdvancedQueryEntity = ((ServerEntities.TreeNodeAdvancedQuery)entity);

                    treeNodeBase = treeNodeAdvancedQueryEntity.TreeNodeBase;
                    break;

                case "TreeNodeAttributeNode":

                    var treeNodeAttributeNode = (ITreeNodeAttributeNode)node;
                    var treeNodeAttributeNodeEntity = ((ServerEntities.TreeNodeAttributeNode)entity);

                    treeNodeBase = treeNodeAttributeNodeEntity.TreeNodeBase;
                    break;

                case "TreeNodeElementNode":

                    var treeNodeElementNode = (ITreeNodeElementNode)node;
                    var treeNodeElementNodeEntity = ((ServerEntities.TreeNodeElementNode)entity);

                    treeNodeBase = treeNodeElementNodeEntity.TreeNodeBase;
                    break;

                case "TreeNodeFolder":

                    var treeNodeFolder = (ITreeNodeFolder)node;
                    var treeNodeFolderEntity = ((ServerEntities.TreeNodeFolder)entity);

                    treeNodeBase = treeNodeFolderEntity.TreeNodeBase;
                    break;

                case "TreeNodePropertyBinding":

                    var treeNodePropertyBinding = (ITreeNodePropertyBinding)node;
                    var treeNodePropertyBindingEntity = ((ServerEntities.TreeNodePropertyBinding)entity);

                    treeNodeBase = treeNodePropertyBindingEntity.TreeNodeBase;

                    if (treeNodePropertyBinding.TreeNodeImage != null)
                    {
                        var treeNodeImageEntity = (ServerEntities.TreeNodeImage)treeNodePropertyBinding.TreeNodeImage.ServerEntity;

                        treeNodeImageEntity.TreeNodeBase = treeNodeBase;
                        treeNodeImageEntity.TreeNodeBaseID = treeNodeBase.TreeNodeBaseID;
                    }

                    break;

                case "TreeNodeQuery":

                    var treeNodeQuery = (ITreeNodeQuery)node;
                    var treeNodeQueryEntity = ((ServerEntities.TreeNodeQuery)entity);

                    treeNodeBase = treeNodeQueryEntity.TreeNodeBase;
                    break;

                case "TreeNodeRootSource":

                    var treeNodeRootSource = (ITreeNodeRootSource)node;
                    var treeNodeRootSourceEntity = ((ServerEntities.TreeNodeRootSource)entity);

                    treeNodeBase = treeNodeRootSourceEntity.TreeNodeBase;
                    break;

                case "TreeNodeTransferMapping":

                    var treeNodeTransferMapping = (ITreeNodeTransferMapping)node;
                    var treeNodeTransferMappingEntity = ((ServerEntities.TreeNodeTransferMapping)entity);

                    treeNodeBase = treeNodeTransferMappingEntity.TreeNodeBase;
                    break;

                case "TreeNodeMenuItem":

                    var treeNodeMenuItem = (ITreeNodeMenuItem)node;
                    var treeNodeMenuItemEntity = ((ServerEntities.TreeNodeMenuItem)entity);

                    treeNodeBase = treeNodeMenuItemEntity.TreeNodeBase;

                    if (treeNodeMenuItem.TreeNodeImage != null)
                    {
                        var treeNodeImageEntity = (ServerEntities.TreeNodeImage)treeNodeMenuItem.TreeNodeImage.ServerEntity;

                        treeNodeImageEntity.TreeNodeBase = treeNodeBase;
                        treeNodeImageEntity.TreeNodeBaseID = treeNodeBase.TreeNodeBaseID;
                    }

                    break;

                default:
                    Debugger.Break();
                    break;
            }

            return context.SubmitChanges();
        }

        public SubmitOperation DeleteNode<T>(T node) where T : ITreeNodeBase
        {
            var entity = ((ITreeNodeServerEntity)node).ServerEntity;
            ServerEntities.TreeNodeBase treeNodeBase = null;

            switch (entity.GetType().Name)
            {
                case "TreeNodeAdvancedQuery":

                    var treeNodeAdvancedQuery = (ITreeNodeAttributeNode)node;
                    treeNodeBase = ((ServerEntities.TreeNodeAdvancedQuery)entity).TreeNodeBase;
                    context.TreeNodeAdvancedQueries.Remove(entity);

                    if (treeNodeAdvancedQuery.ShortcutNode != null)
                    {
                        context.TreeNodeBases.Remove(treeNodeBase);
                    }
                    else
                    {
                        context.TreeNodeBases.Remove(treeNodeBase);
                        context.TreeNodeAdvancedQueries.Remove(entity);
                    }

                    break;

                case "TreeNodeAttributeNode":

                    var treeNodeAttributeNode = (ITreeNodeAttributeNode)node;
                    treeNodeBase = ((ServerEntities.TreeNodeAttributeNode)entity).TreeNodeBase;

                    if (treeNodeAttributeNode.ShortcutNode != null)
                    {
                        context.TreeNodeBases.Remove(treeNodeBase);
                    }
                    else
                    {
                        context.TreeNodeAttributeNodes.Remove(entity);
                    }

                    break;

                case "TreeNodeElementNode":

                    var treeNodeElementNode = (ITreeNodeElementNode)node;
                    treeNodeBase = ((ServerEntities.TreeNodeElementNode)entity).TreeNodeBase;

                    if (treeNodeElementNode.ShortcutNode != null)
                    {
                        context.TreeNodeBases.Remove(treeNodeBase);
                    }
                    else
                    {
                        context.TreeNodeElementNodes.Remove(entity);
                    }

                    break;

                case "TreeNodeFolder":

                    var treeNodeFolder = (ITreeNodeFolder)node;
                    treeNodeBase = ((ServerEntities.TreeNodeFolder)entity).TreeNodeBase;
                    context.TreeNodeFolders.Remove(entity);

                    if (treeNodeFolder.ShortcutNode != null)
                    {
                        context.TreeNodeBases.Remove(treeNodeBase);
                    }
                    else
                    {
                        context.TreeNodeElementNodes.Remove(entity);
                    }

                    break;

                case "TreeNodePropertyBinding":

                    var treeNodePropertyBinding = (ITreeNodePropertyBinding)node;
                    treeNodeBase = ((ServerEntities.TreeNodePropertyBinding)entity).TreeNodeBase;
                    context.TreeNodePropertyBindings.Remove(entity);

                    if (treeNodePropertyBinding.ShortcutNode != null)
                    {
                        context.TreeNodeBases.Remove(treeNodeBase);
                    }
                    else
                    {
                        context.TreeNodeElementNodes.Remove(entity);
                    }

                    break;

                case "TreeNodeQuery":

                    var treeNodeQuery = (ITreeNodeQuery)node;
                    treeNodeBase = ((ServerEntities.TreeNodeQuery)entity).TreeNodeBase;
                    context.TreeNodeQueries.Remove(entity);

                    if (treeNodeQuery.ShortcutNode != null)
                    {
                        context.TreeNodeBases.Remove(treeNodeBase);
                    }
                    else
                    {
                        context.TreeNodeElementNodes.Remove(entity);
                    }

                    break;

                case "TreeNodeRootSource":

                    var treeNodeRootSource = (ITreeNodeRootSource)node;
                    treeNodeBase = ((ServerEntities.TreeNodeRootSource)entity).TreeNodeBase;
                    context.TreeNodeRootSources.Remove(entity);

                    if (treeNodeRootSource.ShortcutNode != null)
                    {
                        context.TreeNodeBases.Remove(treeNodeBase);
                    }
                    else
                    {
                        context.TreeNodeElementNodes.Remove(entity);
                    }

                    break;
                case "TreeNodeTransferMapping":

                    var treeNodeTransferMapping = (ITreeNodeTransferMapping)node;
                    treeNodeBase = ((ServerEntities.TreeNodeTransferMapping)entity).TreeNodeBase;
                    context.TreeNodeTransferMappings.Remove(entity);

                    if (treeNodeTransferMapping.ShortcutNode != null)
                    {
                        context.TreeNodeBases.Remove(treeNodeBase);
                    }
                    else
                    {
                        context.TreeNodeElementNodes.Remove(entity);
                    }

                    break;

                case "TreeNodeMenuItem":

                    var treeNodeMenuItem = (ITreeNodeMenuItem)node;
                    treeNodeBase = ((ServerEntities.TreeNodeMenuItem)entity).TreeNodeBase;
                    context.TreeNodeMenuItems.Remove(entity);

                    if (treeNodeMenuItem.ShortcutNode != null)
                    {
                        context.TreeNodeBases.Remove(treeNodeBase);
                    }
                    else
                    {
                        context.TreeNodeElementNodes.Remove(entity);
                    }

                    break;

                default:
                    Debugger.Break();
                    break;
            }

            return context.SubmitChanges();
        }

        public T GenerateItemFromID<T>(string id) where T : IBase
        {
            var hierarchy = new TestProviderHierarchy<T>();
            var parm = Expression.Parameter(typeof(IBase), "a");

            var expression = Expression.Lambda<Func<T, bool>>
            (
                Expression.Equal
                (
                    LambdaExpression.Property(parm, "ID"),
                    Expression.Constant(id)
                )

            , new ParameterExpression[] { parm }
            );

            expression.Compile();

            var results = hierarchy.Where(expression);

            return results.Single();
        }

        public Type GetNodeIDType(string id)
        {
            var hierarchy = new TestProviderHierarchy<IBase>();
            var subject = hierarchy.GetSubjects(id).Last();

            if (subject == "BasePropertyAttribute")
            {
                return typeof(IAttribute);
            }
            else if (subject == "BasePropertyElement")
            {
                return typeof(IElement);
            }
            else
            {
                Debugger.Break();
            }

            return null;
        }

        public NodeCreateOperation<ITreeNodeBase> CreateShortcut(ITreeNodeBase parent, ITreeNodeBase shortcutNode)
        {
            var parentEntity = (ITreeNodeServerEntity)parent;
            var shortcutEntity = (ITreeNodeServerEntity)shortcutNode.InternalNodeObject;
            var nodeCreateOperation = new NodeCreateOperation<ITreeNodeBase>();

            try
            {
                var treeNodeNewEntity = new ServerEntities.TreeNodeBase
                {
                    ParentNodeID = Guid.Parse(parentEntity.ID),
                    ShortcutNodeID = Guid.Parse(shortcutEntity.ID),
                    Name = shortcutNode.Text
                };

                context.TreeNodeBases.Add(treeNodeNewEntity);

                var submitOperationNode = context.SubmitChanges();

                submitOperationNode.Completed += (sender2, e2) =>
                {
                    ITreeNodeBase newNode = null;

                    switch (shortcutEntity.GetType().Name)
                    {
                        case "TreeNodeAttributeNode":

                            var entityAttribute = new ServerEntities.TreeNodeAttributeNode
                            {
                                TreeNodeBase = treeNodeNewEntity,
                                Attribute = ((ServerEntities.TreeNodeAttributeNode) shortcutEntity.ServerEntity).Attribute
                            };

                            newNode = new TreeNodeAttributeNode(this, entityAttribute)
                            {
                                ShortcutNode =  shortcutNode
                            };
                            
                            break;

                        case "TreeNodeElementNode":

                            var entityElement = new ServerEntities.TreeNodeElementNode
                            {
                                TreeNodeBase = treeNodeNewEntity,
                                Element = ((ServerEntities.TreeNodeElementNode)shortcutEntity.ServerEntity).Element
                            };

                            newNode = new TreeNodeElementNode(this, entityElement)
                            {
                                ShortcutNode = shortcutNode
                            };
                            
                            break;

                        default:
                            Debugger.Break();
                            break;
                    }

                    nodeCreateOperation.SetCreated(parent, newNode);
                };

            }
            catch (Exception ex)
            {
                throw new Exception("Could not create shortcut", ex);
            }

            return nodeCreateOperation;
        }

        public InvokeOperation<bool> OrphansExist()
        {
            var nodeInvokeOperation = new NodeInvokeOperation<bool>();

            var invokeOperation = context.TreeBuilderOrphansExist();

            return invokeOperation;
        }
    }
}
